package com.tengits.mqttmonitor.rnlibs.mqttbroker;

import static android.content.Context.BIND_IMPORTANT;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.tengits.mqttmonitor.broker.MQTTService;
import com.tengits.mqttmonitor.broker.ServerInstance;
import com.tengits.mqttmonitor.rnlibs.RNErrorConstants;
import com.tengits.mqttmonitor.util.Utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import io.moquette.BrokerConstants;
import io.moquette.server.Server;
import io.moquette.spi.impl.subscriptions.Subscription;


public class MQTTServerLibraryModule extends ReactContextBaseJavaModule implements LifecycleEventListener{
    private static final String TAG = MQTTServerLibraryModule.class.getSimpleName();
    private static MQTTService mService;
    private static boolean mBound = false;
    private ReactContext mReactContext = null;
    private File passwordFile = null;
    private BroadcastReceiver mBroadcastReceiver;
    private static Properties props = new Properties();

    public MQTTServerLibraryModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mReactContext = reactContext;
        mReactContext.addLifecycleEventListener(this);
        passwordFile = new File(reactContext.getDir("media", 0).getAbsolutePath() + Utils.PASSWORD_FILE);
        mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, final Intent intent) { // 所有任务完成后，都会发广播过来
                WritableMap map = Arguments.createMap();
                map.putString("event", "close");
                sendEvent(mReactContext, "mqtt_server_event", map);
            }
        };
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.tengits.mqttmonitor.mqttserver.RECEIVER");
        mReactContext.registerReceiver(mBroadcastReceiver, intentFilter);
    }

    @NonNull
    @Override
    public String getName() {
        return "MQTTServerLibraryModule";
    }

    @ReactMethod
    public void addListener(String eventName) {
        // Set up any upstream listeners or background tasks as necessary
    }
    @ReactMethod
    public void removeListeners(Integer count) {
        // Remove upstream listeners, stop unnecessary background tasks
    }

    private void sendEvent(ReactContext reactContext,
                           String eventName,
                           @Nullable WritableMap params) {
        if(!reactContext.hasActiveReactInstance()) {
            return;
        }
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    private void writeToPasswordFile(File passwordFile, String userName, String password) {
        try (FileOutputStream fileOutputStream = new FileOutputStream(passwordFile)) {
            fileOutputStream.write(userName.getBytes());
            fileOutputStream.write(":".getBytes());
            fileOutputStream.write(Utils.getSHA(password).getBytes());
            return;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            MQTTServerLibraryModule.mService = ((MQTTService.LocalBinder) service).getService();
            MQTTServerLibraryModule.mBound = ((MQTTService.LocalBinder) service).getServerStatus();
            if(MQTTServerLibraryModule.mBound) {
                WritableMap map = Arguments.createMap();
                map.putString("event", "connect");
                sendEvent(mReactContext, "mqtt_server_event", map);
            } else {
                WritableMap map = Arguments.createMap();
                map.putString("event", "close");
                sendEvent(mReactContext, "mqtt_server_event", map);
            }
        }

        public void onServiceDisconnected(ComponentName arg0) {
            MQTTServerLibraryModule.mService = null;
            MQTTServerLibraryModule.mBound = false;
            WritableMap map = Arguments.createMap();
            map.putString("event", "close");
            sendEvent(mReactContext, "mqtt_server_event", map);
        }
    };

    @ReactMethod
    public void startMQTTService(ReadableMap options, Boolean autoRestart) {
        if (mBound == true && mService != null) {
            Log.i(TAG, "Service already running");
            WritableMap map = Arguments.createMap();
            map.putString("event", "connect");
            sendEvent(mReactContext, "mqtt_server_event", map);
            return;
        }
        props.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, mReactContext.getExternalFilesDir(null).getAbsolutePath() + File.separator + BrokerConstants.DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME);
        props.setProperty(BrokerConstants.PORT_PROPERTY_NAME, options.getString("port"));
        props.setProperty(BrokerConstants.NEED_CLIENT_AUTH, String.valueOf(options.getBoolean("auth")));
        props.setProperty(BrokerConstants.HOST_PROPERTY_NAME, options.getString("host"));
        props.setProperty(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, options.getString("wsPort"));

        Boolean vAuth = options.getBoolean("auth");
        if(vAuth) {
            writeToPasswordFile(passwordFile, options.getString("userName"), options.getString("password"));
            props.setProperty(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, passwordFile.getAbsolutePath());
        } else {
            props.remove(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME);
        }

        this.startMQTTServiceImpl(props);
    }

    private void startMQTTServiceImpl(Properties props) {
        if (mBound == true && mService != null) {
            Log.i(TAG, "Service already running");
            return;
        }
        Intent serviceIntent = new Intent(getReactApplicationContext(), MQTTService.class);

        Bundle bundle = new Bundle();
        bundle.putSerializable("config", props);
        serviceIntent.putExtras(bundle);

        mReactContext.startService(serviceIntent);
        mReactContext.bindService(new Intent(mReactContext, MQTTService.class), mConnection, BIND_IMPORTANT);
    }

    @ReactMethod
    public void stopMQTTService() {
        this.stopMQTTServiceImpl();
    }

    private void stopMQTTServiceImpl() {
        Server mqttServer = ServerInstance.getServerInstance();
        if(mqttServer != null) {
            try{
                mqttServer.stopServer();
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
        if(mService != null) {
            try {
                mService = null;
                mBound = false;
                mReactContext.unbindService(mConnection);
                Intent serviceIntent = new Intent(mReactContext, MQTTService.class);
                mReactContext.stopService(serviceIntent);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @ReactMethod
    public void getClientConnects(Promise promise) {
        if(mService == null) {
            promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_SERVER_NOT_RUN), "");
        } else {
            try {
                WritableMap map = Arguments.createMap();
                List<Subscription> subscriptionList = ServerInstance.getServerInstance().getSubscriptions();
                for(Subscription sub: subscriptionList) {
                    map.putString("subscriber", sub.toString());
                }
                if(promise != null) {
                    promise.resolve(map);
                }
            } catch (Exception e) {
                if(promise != null) {
                    promise.reject(String.valueOf(RNErrorConstants.ERROR_MQTT_EXCEPTION), e.getMessage());
                }
            }
        }
    }

    @ReactMethod
    public void isMqttServiceRunning(Promise promise) {
        promise.resolve(mService != null && mBound == true);
    }

    @Override
    public void onHostResume() {

    }

    @Override
    public void onHostPause() {

    }

    @Override
    public void onHostDestroy() {
        try{
            stopMQTTService();
            mReactContext.unregisterReceiver(mBroadcastReceiver);
            mReactContext.removeLifecycleEventListener(this);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
